﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Platform.Infrastructure;
using Platform.ObjectModel.Design;
using Platform.ObjectModel.Validation;
using Platform.Storage;

namespace Platform.ObjectModel.Extended
{

	/// <summary>
	/// Permite a personalização do processo de CRUD da biblioteca
	/// </summary>
	public abstract class LibraryPipeline
	{
		private IValidatorFactory validatorFactory;

		/// <summary>
		/// 
		/// </summary>
		/// <param name="library"></param>
		/// <param name="validatorFactory"></param>
		protected LibraryPipeline( Library library, IValidatorFactory validatorFactory )
		{
			Arguments.IsNotNull( "library", library );
			Arguments.IsNotNull( "validatorFactory", validatorFactory );

			this.validatorFactory = validatorFactory;
			this.Library = library;
			this.TokenGenerator = library.TokenGenerator ?? new RandomTokenGeneratorStrategy();
			
		}

		/// <summary>
		/// Obtém a biblioteca que contém os documentos do sistema
		/// </summary>
		public Library Library
		{ get; private set; }

		/// <summary>
		/// Obtém ou define o gerador de tokens para os novos documentos
		/// </summary>
		/// <remarks>
		/// Essa propriedade sempre vai possuir uma instância, nunca é null
		/// </remarks>
		public ITokenGeneratorStrategy TokenGenerator
		{ get; private set; }

		/// <summary>
		/// Obtém o objeto utilizado para persistido para salvar os documentos
		/// </summary>
		public ILibraryStorage Storage
		{ get { return this.Library.Storage; } }

		/// <summary>
		/// Obtém ou define a fectory utilizada para realizar as validaçoes do documento
		/// </summary>
		public IValidatorFactory ValidatorFactory
		{
			get { return this.validatorFactory; }
			set
			{
				Arguments.IsNotNull( "ValidatorFactory", value );
				this.validatorFactory = value;
			}
		}

		/// <summary>
		/// Realiza a criação do contexto de execução da biblioteca
		/// </summary>
		/// <param name="document"></param>
		/// <returns></returns>
		protected DocumentContext CreateContext( DocumentFlake document, Schema schema )
		{
			// Raise? OnDocumentContext Creating
			var context = new DocumentContext( document, schema );
			this.FillStorage( context );
			return context;
			// Raise: OnDocumentContext created
		}

		/// <summary>
		/// Validação 
		/// </summary>
		/// <param name="context"></param>
		protected void ValidateStage( DocumentContext context )
		{
			// Raise OnBeforeValidate

			if ( context.Validator == null )
				context.Validator = this.validatorFactory.Create( context.Schema );

			// Realiza a validação
			ValidationResult result = context.Validator.Validate( context.Document, context );
			context.Result = result;

			// Raise OnAfterValidate
		}

		/// <summary>
		/// Realiza a alteração no documento
		/// </summary>
		/// <param name="context"></param>
		protected abstract void SaveDocuments( DocumentContext context );


		/// <summary>
		/// Adiciona os documentos que serão salvos. Nesse caso todos os documentos
		/// que são relacionados com esse documento
		/// </summary>
		/// <param name="context"></param>
		protected virtual void FillStorage( DocumentContext context )
		{
			// 
			var document = context.Document;

			// Adiciona os documentos que devem ser salvos
			this.SetToStore( context, document, context.Schema );

			// Adiciona para salvar os dados dos documentos relacionados
			foreach ( var relation in context.Schema.Relations )
			{
				var children = LibraryUtility.GetChildren( document, relation.QualifiedName );

				foreach ( var child in children )
					this.SetToStore( context, child, this.Library.Workspace.GetSchema( child.SchemaName ) );

				//if ( children.GetType().Implements<IEnumerable<FieldDescriptor>>() )
				//{
				//    foreach ( var child in children )
				//        this.SetToStore( context, child, this.Library.GetSchema( FieldDescriptor.FieldSchemaNamespace ) );
				//}
				//else
				//{
				//    foreach ( var child in children )
				//        this.SetToStore( context, child, this.Library.GetSchema( child.SchemaName ) );
				//}
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="dataObject"></param>
		/// <param name="schema"></param>
		protected virtual void SetToStore( DocumentContext context, DocumentFlake dataObject, Schema schema )
		{
			Debug.Assert( context != null );
			Debug.Assert( dataObject != null );
			Debug.Assert( schema != null );


			// TODO : Tornar esse método em algo que faça parte do pipeline
			if ( dataObject.Token == null )
				dataObject.Token = this.TokenGenerator.Create();

			context.StorageDocument.Add( new StorageSlot( dataObject, schema ) );
		}

		/// <summary>
		/// Realiza a execução do pipeline
		/// </summary>
		/// <param name="document"></param>
		/// <param name="schema"></param>
		public DocumentContext Run( DocumentFlake document, Schema schema )
		{
			// Cria o context
			var context = this.CreateContext( document, schema );

			// Validação
			this.ValidateStage( context );

			// Salva
			if ( context.Result == null || context.Result.IsValid )
			{
				this.PersistenceStage( context );
			}

			// Raise: OnUpdateFinished

			return context;
		}

		private void PersistenceStage( DocumentContext context )
		{
			// Raise OnBeforeSave

			// O que deve ser feito
			// 1. Pegar todos os documentos relacionados ao objeto pai	
			//			(Resolver problema se o Field e Rule vão herdar de DocumentFlake)
			// 2. Carregar para cada documento os schemas associados a eles
			//			(! Deve ser feita a validação dos outros documentos que são salvos juntos !)
			// 3. Utilizar o método do objeto "Library" para carregar os schemas
			// 4. Ao instanciar o objeto "Library" devem ser registradas os seguintes schemas:
			//		. Schema
			//		. FieldDescriptor
			//		. 


			//this.FillStorage( context );
			this.SaveDocuments( context );
			// Raise OnAfterSave
		}
	}
}
